﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
using System.Linq;
using System.Reflection;
using Caliburn.Micro.BikeWizard.Views;
using Caliburn.Micro.BikeWizard.Services;
using Caliburn.Micro.BikeWizard.Utils;
using Caliburn.Micro.BikeWizard.Events;

namespace Caliburn.Micro.BikeWizard
{
    public class WizardBootstrapper : Bootstrapper<ShellViewModel>
    {
        private CompositionContainer container;

        protected override IEnumerable<Assembly> SelectAssemblies()
        {
            return 
                new List<Assembly>()
                {
                    System.Windows.Application.Current.GetType().Assembly,
                    typeof(EventAggregator).Assembly
                };
        }

        // Don't display root viewmodel on startup
        protected override void OnStartup(object sender, System.Windows.StartupEventArgs e)
        {
            // Get the bikeOptionService from the container
            var bikeOptionService = IoC.Get<IBikeOptionService>();

            // Only after the testdata has been setup, the root viewmodel should be shown
            // We assume that setting up the testdata requires a lot of asynchronous calls 
            var coroutineExecutor = new CoroutineExecutor(new TestDataHelper().SetupTestData(bikeOptionService));
            coroutineExecutor.Completed +=
                (obj, args) =>
                {
                    DisplayRootView();
                };
            coroutineExecutor.Execute(null);
        }

        protected override void Configure()
        {
            container = CompositionHost.Initialize(
                new AggregateCatalog(
                    AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>()
                    )
                );

            var batch = new CompositionBatch();

            var bikeOptionDataStore = new InMemoryDataStore();
            var bikeOptionService = new BikeOptionService(bikeOptionDataStore);
            var eventAggregator = new EventAggregator();

            batch.AddExportedValue<IWindowManager>(new WindowManager());
            batch.AddExportedValue<IEventAggregator>(eventAggregator);
            batch.AddExportedValue<IDataStore>(bikeOptionDataStore);
            batch.AddExportedValue<IBikeOptionService>(bikeOptionService);
            batch.AddExportedValue(container);

            container.Compose(batch);

            ConfigureLogging(eventAggregator);
        }

        protected override object GetInstance(Type serviceType, string key)
        {
            string contract = string.IsNullOrEmpty(key) ? AttributedModelServices.GetContractName(serviceType) : key;
            var exports = container.GetExportedValues<object>(contract);

            if (exports.Count() > 0)
                return exports.First();

            throw new Exception(string.Format("Could not locate any instances of contract {0}.", contract));
        }

        protected override IEnumerable<object> GetAllInstances(Type serviceType)
        {
            return container.GetExportedValues<object>(AttributedModelServices.GetContractName(serviceType));
        }

        protected override void BuildUp(object instance)
        {
            container.SatisfyImportsOnce(instance);
        }

        private void ConfigureLogging(IEventAggregator eventAggregator)
        {
            EventPublishingingLog eventPublishingLog = new EventPublishingingLog(eventAggregator);
            LogManager.GetLog =
                (type) =>
                {
                    return eventPublishingLog;
                };
        }

        private class EventPublishingingLog : ILog
        {
            IEventAggregator eventAggregator { get; set; }

            public EventPublishingingLog(IEventAggregator eventAggregator)
            {
                this.eventAggregator = eventAggregator;
            }

            public void Info(string format, params object[] args)
            {
                string msg = String.Format(format, args);
                PublishLogEvent<InfoLogEvent>(new InfoLogEvent(GetMessage(format, args)));
            }

            public void Warn(string format, params object[] args)
            {
                PublishLogEvent<WarningLogEvent>(new WarningLogEvent(GetMessage(format, args)));
            }

            public void Error(Exception exception)
            {
                string msg = exception.Message;
                PublishLogEvent<ErrorLogEvent>(new ErrorLogEvent(exception));
            }

            private string GetMessage(string format, params object[] args)
            {
                string msg = String.Format(format, args);
                return msg;
            }

            private void PublishLogEvent<TLogEvent>(TLogEvent logEvent) where TLogEvent : class
            {
                eventAggregator.Publish<TLogEvent>(logEvent);
            }
        }
    }
}
